package com.hinacom.pix.serviceproxy;

import ca.uhn.hl7v2.parser.PipeParser;
import com.hinacom.pix.tls.ClientSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by fyu on 2016/8/22.
 */
public class PixClient {
    private static final Log ourLog;
    private String host = null;
    private int port = 0;
    private Socket socket = null;
    private OutputStream os = null;
    private InputStream is = null;
    private ReceiveListener listener;

    static {
        ourLog = LogFactory.getLog(PixClient.class);
    }

    public PixClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void openSocket() throws IOException {
        ResourceBundle bundle = PropertyResourceBundle.getBundle("pix");

        if (bundle.containsKey("tls.allow") && bundle.getString("tls.allow").equals("true")) {
            String trustStore = bundle.getString("trustStore");
            String trustStorePassword = bundle.getString("trustStorePassword");
            String keyStore = bundle.getString("keyStore");
            String keyStorePassword = bundle.getString("keyStorePassword");

            ClientSocketFactory clientSocketFactory = new ClientSocketFactory(keyStore, keyStorePassword, trustStore, trustStorePassword);
            this.socket = clientSocketFactory.createSocket(this.host, this.port);
        }
        else
        {
            this.socket = new Socket(this.host, this.port);
        }
        this.socket.setSoLinger(true, 1000);
        this.os = this.socket.getOutputStream();
        this.is = this.socket.getInputStream();
    }

    public void closeSocket() {
        try {
            Socket e = this.socket;
            this.socket = null;
            if (e != null) {
                e.close();
            }
        } catch (Exception var2) {
            ourLog.error(var2);
        }

    }

    public int process(String message) throws IOException {
        InputStream msgInputStream = new ByteArrayInputStream(message.getBytes("UTF-8"));
        int result = this.process(msgInputStream);
        return result;
    }

    public int process(InputStream theMsgInputStream) throws IOException {
        new PipeParser();
        BufferedReader in = new BufferedReader(new CommentFilterReader(new InputStreamReader(theMsgInputStream)));
        StringBuffer rawMsgBuffer = new StringBuffer();

        int data;
        while ((data = in.read()) >= 0) {
            rawMsgBuffer.append((char) data);
        }

        String[] messages = getHL7Messages(rawMsgBuffer.toString());
        int retVal = 0;
        long startTime = (new GregorianCalendar()).getTimeInMillis();

        for (int i = 0; i < messages.length; ++i) {
            this.sendMessage(messages[i]);
            this.readAck();
            ++retVal;
        }

        long var14 = (new GregorianCalendar()).getTimeInMillis();
        long elapsedTime = (var14 - startTime) / 1000L;
        ourLog.info(retVal + " messages sent.");
        ourLog.info("Elapsed Time in seconds:  " + elapsedTime);
        return retVal;
    }

    private String readAck() throws IOException {
        StringBuffer stringbuffer = new StringBuffer();
        boolean i = false;

        int i1;
        do {
            i1 = this.is.read();
            if (i1 == -1) {
                return null;
            }

            stringbuffer.append((char) i1);
        } while (i1 != 28);


        String buffer = stringbuffer.toString();
        if (this.listener != null) {
            this.listener.onReceive(buffer);
        }
        return buffer;
    }

    public static String[] getHL7Messages(String theSource) {
        ArrayList messages = new ArrayList(20);
        Pattern startPattern = Pattern.compile("^MSH", 8);
        Matcher startMatcher = startPattern.matcher(theSource);

        while (startMatcher.find()) {
            String messageExtent = getMessageExtent(theSource.substring(startMatcher.start()), startPattern);
            char fieldDelim = messageExtent.charAt(3);
            Pattern segmentPattern = Pattern.compile("^[A-Z\\d]{3}\\" + fieldDelim + ".*$", 8);
            Matcher segmentMatcher = segmentPattern.matcher(messageExtent);
            StringBuffer msg = new StringBuffer();

            while (segmentMatcher.find()) {
                msg.append(segmentMatcher.group().trim());
                msg.append('\r');
            }

            messages.add(msg.toString());
        }

        return (String[]) ((String[]) messages.toArray(new String[0]));
    }

    private static String getMessageExtent(String theSource, Pattern theStartPattern) {
        Matcher startMatcher = theStartPattern.matcher(theSource);
        if (!startMatcher.find()) {
            throw new IllegalArgumentException(theSource + "does not contain message start pattern" + theStartPattern.toString());
        } else {
            int start = startMatcher.start();
            int end = theSource.length();
            if (startMatcher.find()) {
                end = startMatcher.start();
            }

            return theSource.substring(start, end).trim();
        }
    }


    private void sendMessage(String theMessage) throws IOException {
        this.os.write("\u000b".getBytes());
        this.os.write(theMessage.getBytes());
        this.os.write("\u001c".getBytes());
        this.os.write(13);
        this.os.flush();
        ourLog.info("Sent: " + theMessage);
    }

    public static class CommentFilterReader extends PushbackReader {
        private final char[] startCPPComment = new char[]{'/', '*'};
        private final char[] endCPPComment = new char[]{'*', '/'};
        private final char[] startCComment = new char[]{'/', '/'};
        private final char[] endCComment = new char[]{'\n'};
        private final char[] protocolDelim = new char[]{':', '/', '/'};

        public CommentFilterReader(Reader in) {
            super(in, 5);
        }

        public int read() throws IOException {
            int ret;
            if (!this.atSequence(this.protocolDelim)) {
                if (!this.atSequence(this.startCPPComment)) {
                    if (this.atSequence(this.startCComment)) {
                        while (!this.atSequence(this.endCComment)) {
                            super.read();
                        }

                        for (ret = 0; ret < this.endCComment.length; ++ret) {
                            super.read();
                        }
                    }
                } else {
                    while (!this.atSequence(this.endCPPComment)) {
                        super.read();
                    }

                    for (ret = 0; ret < this.endCPPComment.length; ++ret) {
                        super.read();
                    }
                }
            }

            ret = super.read();
            if (ret == '\uffff') {
                ret = -1;
            }

            return ret;
        }

        public int read(char[] cbuf, int off, int len) throws IOException {
            int i = -1;
            boolean done = false;

            while (true) {
                ++i;
                if (i >= len) {
                    break;
                }

                int next = this.read();
                if (next == '\uffff' || next == -1) {
                    done = true;
                    break;
                }

                cbuf[off + i] = (char) next;
            }

            if (i == 0 && done) {
                i = -1;
            }

            return i;
        }

        private boolean atSequence(char[] sequence) throws IOException {
            boolean result = true;
            int i = -1;
            int[] data = new int[sequence.length];

            while (true) {
                ++i;
                if (i >= sequence.length || !result) {
                    for (int j = i - 1; j >= 0; --j) {
                        this.unread(data[j]);
                    }

                    return result;
                }

                data[i] = super.read();
                if ((char) data[i] != sequence[i]) {
                    result = false;
                }
            }
        }
    }

    public void setReceiveListener(ReceiveListener listener) {
        this.listener = listener;
    }


}
